package com.banmao.youZone.api;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSON;
import com.banmao.youZone.consts.CommonConst;
import com.banmao.youZone.entity.Holiday;
import com.banmao.youZone.entity.TaskTrigger;
import com.banmao.youZone.entity.UserVO;
import com.banmao.youZone.entity.vo.JobConfig;
import com.banmao.youZone.job.DailyJob;
import com.banmao.youZone.job.OnceJob;
import com.banmao.youZone.mapper.HolidayMapper;
import com.banmao.youZone.mapper.TaskTriggerMapper;
import com.banmao.youZone.mapper.UserMapper;
import com.banmao.youZone.util.CacheUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.BatchResult;
import org.quartz.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ApiService {

    @Resource
    private TaskTriggerMapper taskTriggerMapper;

    @Resource
    private HolidayMapper holidayMapper;

    @Resource
    private UserMapper userMapper;

    @Resource
    private Scheduler scheduler;

    public List<TaskTrigger> getTaskTrigger(UserVO userVO) {
        List<TaskTrigger> taskTriggers = Optional.ofNullable(taskTriggerMapper
                        .selectList(Wrappers.<TaskTrigger>lambdaQuery()
                                .eq(TaskTrigger::getUserId, userVO.getUserId())
                                .orderByAsc(TaskTrigger::getIdx)))
                .orElse(new ArrayList<>());
        taskTriggers.forEach(t -> {
            if (t.getType().equals(1)) {
                ArrayList<String> section = new ArrayList<>() {{
                    add(t.getTimeSectionBegin());
                    add(t.getTimeSectionEnd());
                }};
                t.setTimeSection(section);
            }
        });
        return taskTriggers;
    }

    // 获取当前时间之后 forecastDays 天的执行计划
    public List<JobConfig.ExecuteForecast> getExecuteForecast(Integer userId, int forecastDays) {

        UserVO userVO = userMapper.selectOne(Wrappers.<UserVO>lambdaQuery()
                .eq(UserVO::getUserId, userId), true);
        if (!userVO.getExecuteJob()) {
            return new ArrayList<>();
        }

        List<TaskTrigger> taskTriggers = Optional.ofNullable(taskTriggerMapper
                        .selectList(Wrappers.<TaskTrigger>lambdaQuery()
                                .eq(TaskTrigger::getUserId, userId)
                                .orderByAsc(TaskTrigger::getIdx)))
                .orElse(new ArrayList<>())
                .stream()
                .filter(TaskTrigger::getOpen)
                .toList();
        if (taskTriggers.isEmpty()) {
            return new ArrayList<>();
        }

        // 获取之后 n 天的 DateTime
        DateTime offset = DateUtil.date();
        offset.setMutable(false);
        ArrayList<DateTime> forecastDateTime = new ArrayList<>();
        do {
            forecastDateTime.add(offset);
            offset = offset.offset(DateField.DAY_OF_YEAR, 1);
        } while (forecastDateTime.size() < forecastDays);

        ArrayList<JobConfig.ExecuteForecast> executeForecasts = new ArrayList<>();
        for (TaskTrigger t : taskTriggers) {

            for (DateTime dateTime : forecastDateTime) {

                Integer type = t.getType();
                if (type == 1) {
                    String format = String.format("%s %s - %s", dateTime.toString(DatePattern.NORM_DATE_FORMAT),
                            t.getTimeSectionBegin(), t.getTimeSectionEnd());
                    JobConfig.ExecuteForecast jobExecuteForecast = JobConfig.ExecuteForecast.builder()
                            .jobTime(format).build();

                    if (t.getMode() == 1) {
                        if (judgeNeedExecute(dateTime)) {
                            executeForecasts.add(jobExecuteForecast);
                        }
                    } else {
                        if (!DateUtil.isWeekend(dateTime)) {
                            executeForecasts.add(jobExecuteForecast);
                        }
                    }
                } else {
                    String dateTimeStr = dateTime.toString(DatePattern.NORM_DATE_FORMAT);
                    String precisitonDate = DateUtil.parse(t.getPrecisionDateTime(), DatePattern.NORM_DATETIME_MINUTE_FORMAT)
                            .toString(DatePattern.NORM_DATE_FORMAT);
                    if (StringUtils.compare(dateTimeStr, precisitonDate) == 0) {
                        JobConfig.ExecuteForecast jobExecuteForecast = JobConfig.ExecuteForecast.builder()
                                .jobTime(t.getPrecisionDateTime()).build();
                        executeForecasts.add(jobExecuteForecast);
                    }
                }
            }
        }

        executeForecasts.sort((f1, f2) -> StringUtils.compare(f1.getJobTime(), f2.getJobTime()));

        return executeForecasts;
    }

    public boolean judgeNeedExecute(DateTime judgeDate) {

        Map<String, Holiday> holidayMap = CacheUtil.getHolidayMap();
        Map<String, Holiday> workdayMap = CacheUtil.getWorkdayMap();

        // 获取节假日和周末，得到工作日
        if (holidayMap == null || holidayMap.isEmpty()) {
            throw new RuntimeException("节假日未获取");
        }
        // 如果 dateTime 是节假日，就没问题
        if (holidayMap.containsKey(DateUtil.format(judgeDate, DatePattern.NORM_DATE_FORMAT))) {
            return false;
        } else {
            // 否则，判断 dateTime 是否工作日或调休日
            return workdayMap.containsKey(DateUtil.format(judgeDate, DatePattern.NORM_DATE_FORMAT))
                    || !DateUtil.isWeekend(judgeDate);
        }
    }

    private static ArrayList<DateTime> handleSectionTrigger(TaskTrigger t, Map<String, Holiday> allHolidayMap,
                                                            Map<String, Holiday> allWorkdayMap) {
        // 区间段
        // 先获取从今天开始之后十天的
        DateTime currentDateTime = DateUtil.date();
        ArrayList<DateTime> dateTimes = new ArrayList<>();
        while (dateTimes.size() < 10) {
            currentDateTime = currentDateTime.offset(DateField.DAY_OF_YEAR, 1);
            dateTimes.add(currentDateTime);
        }

        Integer mode = t.getMode();

        Iterator<DateTime> iterator = dateTimes.iterator();
        while (iterator.hasNext()) {
            DateTime dateTime = iterator.next();
            if (mode == 1) {
                // 获取节假日和周末，得到工作日
                if (allHolidayMap == null || allHolidayMap.isEmpty()) {
                    throw new RuntimeException("节假日未获取");
                }
                // 如果 dateTime 是节假日，就没问题
                if (allHolidayMap.containsKey(DateUtil.format(dateTime, DatePattern.NORM_DATE_FORMAT))) {
                    continue;
                } else {
                    // 否则，判断 dateTime 是否工作日或调休日
                    if (allWorkdayMap.containsKey(DateUtil.format(dateTime, DatePattern.NORM_DATE_FORMAT))
                            || !DateUtil.isWeekend(dateTime)) {
                        // 是调休或者非周末，就剔除
                        iterator.remove();
                    }
                }
            } else {
                // 仅判断是否非周末
                if (!DateUtil.isWeekend(dateTime)) {
                    iterator.remove();
                }
            }
        }
        return dateTimes;

    }


    @Transactional(rollbackFor = Throwable.class)
    public void saveTriggers(List<TaskTrigger> taskTriggers, Integer userId, String userName) throws SchedulerException {
        taskTriggers.forEach(t -> t.handleTaskTrigger(userId, userName));

        int delete = taskTriggerMapper.delete(Wrappers.<TaskTrigger>lambdaQuery()
                .eq(TaskTrigger::getUserId, userId));
        log.info("[submitTasks]用户:[{}]{},已删除{}条记录", userId, userName, delete);

        deleteJobs(userId);

        List<BatchResult> insert = taskTriggerMapper.insert(taskTriggers);
        for (BatchResult batchResult : insert) {
            int[] updateCounts = batchResult.getUpdateCounts();
            log.info("[submitTasks]用户:[{}]{},已更新结果:{}", userId,
                    userName, updateCounts.length);
        }

        newJobs(taskTriggers.stream().filter(TaskTrigger::getOpen).toList(), userId);

    }

    private void newJobs(List<TaskTrigger> tasks, Integer userId) throws SchedulerException {

        if (tasks == null || tasks.isEmpty()) {
            return;
        }

        Set<Trigger> dailyTriggerSet = new HashSet<>();
        Set<Trigger> onceTriggerSet = new HashSet<>();

        Map<String, String> dailyTaskMap = tasks.stream().filter(t -> t.getType() == 1)
                .collect(Collectors.toMap(TaskTrigger::getId,
                        JSON::toJSONString, (oldV, newV) -> oldV));

        Map<String, String> onceTaskMap = tasks.stream().filter(t -> t.getType() == 2)
                .collect(Collectors.toMap(TaskTrigger::getId,
                        JSON::toJSONString, (oldV, newV) -> oldV));

        JobKey dailyJobKey = JobKey.jobKey(String.valueOf(userId), DailyJob.class.getSimpleName());
        JobDetail dailyJobDetail = JobBuilder.newJob(DailyJob.class)
                .withIdentity(dailyJobKey)
                .usingJobData(CommonConst.USER_ID, userId)
                .storeDurably()
                .build();

        JobKey onceJobKey = JobKey.jobKey(String.valueOf(userId), OnceJob.class.getSimpleName());
        JobDetail onceJobDetail = JobBuilder.newJob(OnceJob.class)
                .withIdentity(onceJobKey)
                .usingJobData(CommonConst.USER_ID, userId)
                .storeDurably()
                .build();

        for (int i = 0; i < tasks.size(); i++) {
            TaskTrigger task = tasks.get(i);

            if (task.getType() == 1) {
                String timeSectionBegin = task.getTimeSectionBegin();
                String[] split = timeSectionBegin.split(":");
                CronScheduleBuilder scheduleBuilder =
                        CronScheduleBuilder.cronSchedule(String.format("0 %s %s * * ?", split[1], split[0]))
                                .inTimeZone(TimeZone.getTimeZone("Asia/Shanghai"))
                                .withMisfireHandlingInstructionDoNothing();
                // 创建任务触发器
                Trigger trigger = TriggerBuilder.newTrigger()
                        .forJob(dailyJobDetail)
                        .usingJobData(CommonConst.TASK_JSON, JSON.toJSONString(task))
                        .withIdentity(dailyJobKey + "-begin-" + i)
                        .withSchedule(scheduleBuilder)
                        .build();
                dailyTriggerSet.add(trigger);

//                String timeSectionEnd = task.getTimeSectionEnd();
//                split = timeSectionEnd.split(":");
//                scheduleBuilder =
//                        CronScheduleBuilder.cronSchedule(String.format("0 %s %s * * ?", split[1], split[0]))
//                                .inTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
//                // 创建任务触发器
//                trigger = TriggerBuilder.newTrigger()
//                        .forJob(dailyJobDetail)
//                        .withIdentity(dailyJobKey + "-end-" + i)
//                        .withSchedule(scheduleBuilder)
//                        .build();
//                dailyTriggerSet.add(trigger);
            } else {
                String precisionDateTime = task.getPrecisionDateTime();
                DateTime precisTime = DateUtil.parse(precisionDateTime, DatePattern.NORM_DATETIME_MINUTE_FORMAT);
                SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                        .withRepeatCount(0)
                        .withMisfireHandlingInstructionNextWithRemainingCount();
                Trigger trigger = TriggerBuilder.newTrigger()
                        .forJob(onceJobDetail)
                        .usingJobData(CommonConst.TASK_JSON, JSON.toJSONString(task))
                        .startAt(precisTime)
                        .withIdentity(onceJobKey + "-" + i)
                        .withSchedule(scheduleBuilder)
                        .build();
                onceTriggerSet.add(trigger);
            }

        }

        // boolean replace 表示启动时对数据库中的quartz的任务进行覆盖。
        if (!dailyTriggerSet.isEmpty()) {
            scheduler.scheduleJob(dailyJobDetail, dailyTriggerSet, true);
        }

        if (!onceTriggerSet.isEmpty()) {
            scheduler.scheduleJob(onceJobDetail, onceTriggerSet, true);
        }
    }

    public void deleteJobs(Integer userId) throws SchedulerException {
        JobKey dailyJobKey = JobKey.jobKey(String.valueOf(userId), DailyJob.class.getSimpleName());
        JobKey onceJobKey = JobKey.jobKey(String.valueOf(userId), OnceJob.class.getSimpleName());

        ArrayList<JobKey> jobKeys = new ArrayList<>() {{
            add(dailyJobKey);
            add(onceJobKey);
        }};
        scheduler.deleteJobs(jobKeys);

    }

}
